# coding:utf-8
import copy

import json
import socket
import time


from block_chain import BlockChain
from cnrp import LocalCNRP
from data_bundle import DataBundle
from network import P2p, PeerServer, TCPServer
from node import Node
from node_file import NodeFile
from transaction import TransactionUpload, TransactionRequest, Transaction, TransactionShare, TransactionEvaluate
from txpool import TxPool
from utils_lib.encrypt import encrypt_rsa, get_key_obj_from_str, get_pub_key_obj_from_str, decrypt_rsa
from conf import bootstrap_host
from utils_lib.utils import convert_bytes_to_str


# 执行动作
class Action(object):

    def __init__(self):
        self.bc = BlockChain()

    @staticmethod
    def server_start():
        tcpserver = TCPServer()
        tcpserver.listen()
        tcpserver.run()

        p2p = P2p()
        server = PeerServer()
        server.run(p2p)
        p2p.run()

        return 0

    @staticmethod
    def get_local_node_info():
        # 获取本地node信息
        node_file = NodeFile()
        if not node_file.has_local_node():
            return -1, 'no local node'
        node = node_file['local_node']
        local_cnrp = LocalCNRP()

        res_dict = {
            'node_private_key': convert_bytes_to_str(node.node_private_key),
            'node_public_key': node.node_public_key,
            'node_address': node.address,
            'node_rsa_public_key': node.node_rsa_public_key,
            'node_rsa_private_key': node.node_rsa_private_key,
            'sensor_list': node.sensor_list,
            'data_key_dict': node.data_key_dict,
            'local_cnrp': local_cnrp.get_complete_dict(),
        }
        return 0, res_dict

    # find_condition <dict>: 查找条件 height / block_hash / tx_id /
    # business_id / node_address subject / tx_type / data_id /
    # 其中 node_address 和 subject 必须同时出现或同时不出现
    # 查找结果为 list 元素为 block
    # 注意：应确保输入字典为已知合法字段 height 为int
    # 注意：tx_type 必须为合法字段组成的 list
    # @staticmethod
    def find_blocks(self, find_condition: dict):

        field_list = ['height', 'block_hash', 'tx_id', 'business_id', 'node_address', 'subject', 'tx_type', 'data_id']
        type_dict = {'upload': 0, 'request': 1, 'share': 2, 'evaluate': 3}

        _find_condition = {}
        # 过滤 condition
        for key, value in find_condition.items():
            if key in field_list:
                _find_condition[key] = value

        find_condition = _find_condition

        # bc = BlockChain()

        # 查询-1
        if 'height' in find_condition:
            target_height = find_condition['height']
            if isinstance(target_height, str):
                target_height = int(target_height)
            cur_height = self.bc.get_last_block().block_header.height
            if 0 < target_height <= cur_height:
                return [self.bc.get_block_by_height(target_height)]
        if 'block_hash' in find_condition:
            return [self.bc.get_block_by_hash(find_condition['block_hash'])]
        if 'tx_id' in find_condition:
            return [self.bc.find_block_by_id(find_condition['tx_id'])]

        def _get_attr(_block, _attr):
            if _attr == 'node_address':
                return _block.transactions.tx_content.public_key_hash
            elif _attr == 'tx_type':
                return _block.transactions.tx_type
            elif _attr == 'data_id':
                return _block.transactions.data_id
            elif _attr == 'business_id':
                return _block.transactions.business_id
            else:
                return[]

        # target_node_address = find_condition['node_address']
        # _func_judge_address = _judge_address_unequal
        # if target_node_address[0] == '-':
        #     _func_judge_address = _judge_address_equal
        #     target_node_address = target_node_address[1:]
        # for i in range(len(block_list) - 1, -1, -1):
        #     if _func_judge_address(_get_attr(block_list[i], 'node_address'), target_node_address):
        #         del block_list[i]
        #
        def _judge_node_address(_find_condition_address, _block_list):
            _block_list_copy = copy.deepcopy(_block_list)
            if _find_condition_address[0] == '-':
                _find_condition_address = _find_condition_address[1:]
                for _i in range(len(_block_list_copy) - 1, -1, -1):
                    if _get_attr(block_list[_i], 'node_address') == _find_condition_address:
                        del _block_list_copy[_i]
            else:
                for _i in range(len(_block_list_copy) - 1, -1, -1):
                    if _get_attr(block_list[_i], 'node_address') != _find_condition_address:
                        del _block_list_copy[_i]
            return _block_list_copy

        # def _judge_address_equal(enumeration_address, condition_address):
        #     return enumeration_address == condition_address
        #
        # def _judge_address_unequal(enumeration_address, condition_address):
        #     return enumeration_address != condition_address

        # 查询-2
        if 'business_id' in find_condition:
            block_list = self.bc.find_block_by_business_id(find_condition['business_id'])
            if 'tx_type' in find_condition:
                for i in range(len(block_list)-1, -1, -1):
                    if _get_attr(block_list[i], 'tx_type') not in find_condition['tx_type']:
                        del block_list[i]
                # return [block_list[type_dict[find_condition['tx_type']]]]
            if 'node_address' in find_condition:
                block_list = _judge_node_address(find_condition['node_address'], block_list)

                # target_node_address = find_condition['node_address']
                # _func_judge_address = _judge_address_unequal
                # if target_node_address[0] == '-':
                #     _func_judge_address = _judge_address_equal
                #     target_node_address = target_node_address[1:]
                # for i in range(len(block_list)-1, -1, -1):
                #     if _func_judge_address(_get_attr(block_list[i], 'node_address'), target_node_address):
                #         del block_list[i]

                # if _get_attr(block_list[0], 'node_address') == find_condition['node_address']:
                #     return [block_list[0], block_list[2]]
                # elif _get_attr(block_list[1], 'node_address') == find_condition['node_address']:
                #     return [block_list[1], block_list[3]]
                # else:
                #     return []
            return block_list

        # 查询-3
        if 'node_address' in find_condition:
            # if 'tx_type' not in find_condition:
            #     find_condition['tx_type'] = 'all'
            if 'subject' in find_condition:
                block_list = self.bc.find_xtype_block_relate_address(find_condition['node_address'], 'all', find_condition['subject'])
                if 'tx_type' in find_condition:
                    for i in range(len(block_list)-1, -1, -1):
                        if _get_attr(block_list[i], 'tx_type') not in find_condition['tx_type']:
                            del block_list[i]
                if 'data_id' in find_condition:
                    for i in range(len(block_list), -1, -1):
                        if _get_attr(block_list[i], 'data_id') != find_condition['data_id']:
                            del block_list[i]
                return block_list
            else:
                block_list = []
                if 'tx_type' in find_condition:
                    for single_type in find_condition['tx_type']:
                        block_list += self.bc.find_block_by_type(single_type)
                    if 'data_id' in find_condition:
                        for i in range(len(block_list), -1, -1):
                            if _get_attr(block_list[i], 'data_id') != find_condition['data_id']:
                                del block_list[i]
                else:
                    if 'data_id' in find_condition:
                        block_list = self.bc.find_block_by_data_id(find_condition['data_id'])
                    else:
                        return []

                block_list = _judge_node_address(find_condition['node_address'], block_list)

                # target_node_address = find_condition['node_address']
                # _func_judge_address = _judge_address_unequal
                # if target_node_address[0] == '-':
                #     _func_judge_address = _judge_address_equal
                #     target_node_address = target_node_address[1:]
                # for i in range(len(block_list) - 1, -1, -1):
                #     if _func_judge_address(_get_attr(block_list[i], 'node_address'), target_node_address):
                #         del block_list[i]

                return block_list

        # 查询-4
        if 'tx_type' in find_condition:
            block_list = []
            for t in find_condition['tx_type']:
                block_list += self.bc.find_block_by_type(t)

            if 'data_id' in find_condition:
                for i in range(len(block_list)-1, -1, -1):
                    if _get_attr(block_list[i], 'data_id') != find_condition['data_id']:
                        del block_list[i]
            return block_list

        # 查询-5
        if 'data_id' in find_condition:
            return self.bc.find_block_by_data_id(find_condition['data_id'])

        return []

    # @staticmethod
    def upload(self, upload_data, data_describe):
        # bc = BlockChain()

        # 获取本地node信息
        node_file = NodeFile()
        if not node_file.has_local_node():
            return -1, 'no local node'
        node = node_file['local_node']

        # 设置data_id
        data_id = str(time.time()) + node.address

        # 获取该数据集的对称秘钥
        node.generate_node_symmetric_key(data_id)
        _symmetric_key = node.get_node_symmetric_key(data_id)

        # 更新本地node文件
        node_file['local_node'] = node
        node_file.save()

        # 创建data_bundle
        data_bundle = DataBundle(data_id, upload_data, node.address)
        data_bundle.set_time()
        data_bundle.set_describe(data_describe)
        data_bundle.encrypt(_symmetric_key)

        # 创建block
        transaction_upload = TransactionUpload(node.node_public_key, node.reputation, node.address)
        transaction_upload.set_data(data_bundle.serialize())

        # 完成 tx 设置 -2021.10.09
        tx = Transaction(transaction_upload)
        tx.set_data_id(data_id)
        tx.set_business_id()
        tx.set_previous_tx_id()
        tx.set_id()
        tx.sign(node.node_private_key)

        tx_pool = TxPool()
        tx_pool.add(tx)

        server = PeerServer()
        server.broadcast_tx(tx)
        if tx_pool.is_full():
            self.bc.add_block(tx_pool.txs[0])
            tx_pool.clear()

        # print('upload state: success, tx id is: %s' % tx.tx_id)

        # state code: 0 means success
        return 0, tx.tx_id

    # @staticmethod
    def request(self, tx_id):
        # 获取本地node信息
        node_file = NodeFile()
        if not node_file.has_local_node():
            return -1, 'no local node'
        node = node_file['local_node']

        # bc = BlockChain()
        # upload_tx = self.bc.find_xtype_tx_by_data_id(data_id, 'upload')[0]
        upload_tx = self.bc.find_tx_by_id(tx_id)
        if not upload_tx:
            # print('Not found: No block was found with this data id.')
            return -2, 'not found block'
        elif upload_tx.tx_type != 'upload':
            # print('Wrong request type: The block you ask does not have data.')
            return -3, 'wrong request type'
        elif upload_tx.tx_content.source_node_address == node.address:
            return 1, 'data owner is you'
        else:
            transaction_request = TransactionRequest(node.node_public_key, node.reputation, node.address,
                                                     node.node_rsa_public_key, upload_tx.tx_id)
            tx = Transaction(transaction_request)
            tx.set_data_id(upload_tx.data_id)
            tx.set_business_id(upload_tx.data_id + node.address)
            tx.set_previous_tx_id()
            tx.set_id()
            tx.sign(node.node_private_key)

            tx_pool = TxPool()
            tx_pool.add(tx)
            server = PeerServer()
            server.broadcast_tx(tx)
            if tx_pool.is_full():
                self.bc.add_block(tx_pool.txs[0])
                tx_pool.clear()

            # print('Request state: success, tx id is: %s' % tx.tx_id)
            return 0, tx.tx_id

    # @staticmethod
    def share(self, prev_tx_id):
        request_tx_id = prev_tx_id

        # 获取本地node信息
        node_file = NodeFile()
        if not node_file.has_local_node():
            return -1, 'no local node'
        node = node_file['local_node']

        # bc = BlockChain()
        request_tx = self.bc.find_tx_by_id(request_tx_id)
        upload_tx = self.bc.find_tx_by_id(request_tx.tx_content.previous_tx_id)

        if not request_tx:
            # print('Not found: No block was found with this tx id.')
            return -2, 'not found block'
        elif request_tx.tx_type != 'request':
            # print('Wrong request type: The block you ask does not have request.')
            return -3, 'wrong request type'
        elif node.node_public_key != upload_tx.tx_content.source_node_address:
            # print('Reject command: Do not have permission to publish this block because no condition.')
            return -4, 'no authority'
        elif not self.bc.verify_business_process(node.address, request_tx.tx_id, 'share'):
            return -4, 'business process is illegal'
        else:
            transaction_share = TransactionShare(node.node_public_key, node.reputation,
                                                 node.address, request_tx.tx_id)
            # 设置加密 data key
            _rsa_pub_key, _rsa_pri_key = get_key_obj_from_str(node.node_rsa_public_key, node.node_rsa_private_key)
            key_ciphertext = encrypt_rsa(
                node.get_node_symmetric_key(request_tx.data_id),
                get_pub_key_obj_from_str(request_tx.tx_content.source_rsa_public_key))

            tx = Transaction(transaction_share)
            tx.set_data_id(request_tx.data_id)
            tx.set_business_id(request_tx.business_id)
            tx.set_previous_tx_id()
            tx.tx_content.set_key_ciphertext(key_ciphertext)
            tx.set_id()
            tx.sign(node.node_private_key)

            tx_pool = TxPool()
            tx_pool.add(tx)
            server = PeerServer()
            server.broadcast_tx(tx)
            if tx_pool.is_full():
                self.bc.add_block(tx_pool.txs[0])
                tx_pool.clear()

            # print('Share state: success, tx id is: %s' % tx.tx_id)
            return 0, tx.tx_id

    # @staticmethod
    def evaluate(self, prev_tx_id, data_score):
        if isinstance(data_score, str):
            data_score = int(data_score)

        if data_score < -10 or data_score > 10:
            return -6, 'wrong score'

        data_score = (data_score + 10) / 20

        share_tx_id = prev_tx_id

        # 获取本地node信息
        node_file = NodeFile()
        if not node_file.has_local_node():
            return -1, 'no local node'
        node = node_file['local_node']

        # bc = BlockChain()
        share_tx = self.bc.find_tx_by_id(share_tx_id)
        request_tx = self.bc.find_tx_by_id(share_tx.tx_content.previous_tx_id)
        if not share_tx:
            # print('No found: No block was found with this tx id.')
            return -2, 'not found block'
        elif share_tx.tx_type != 'share':
            # print('Wrong request type: The block you ask does not have share.')
            return -3, 'wrong request type'
        elif node.node_public_key != request_tx.tx_content.source_node_address:
            # print('Reject command: Do not have permission to publish this block because no condition.')
            return -4, 'no authority'
        elif not self.bc.verify_business_process(node.address, share_tx.tx_id, 'evaluate'):
            return -5, 'business process is illegal'
        else:
            transaction_evaluate = TransactionEvaluate(node.node_public_key, node.reputation, node.address,
                                                       share_tx.tx_id, data_score)

            tx = Transaction(transaction_evaluate)

            tx.set_data_id(share_tx.data_id)
            tx.set_business_id(share_tx.business_id)
            tx.set_previous_tx_id()
            tx.set_id()
            tx.sign(node.node_private_key)

            tx_pool = TxPool()
            tx_pool.add(tx)
            server = PeerServer()
            server.broadcast_tx(tx)
            if tx_pool.is_full():
                self.bc.add_block(tx_pool.txs[0])
                tx_pool.clear()

            # print('Share state: success, tx id is: %s' % tx.tx_id)
            return 0, tx.tx_id

    # @staticmethod
    def unpack_data(self, share_tx_id):

        # 获取本地node信息
        node_file = NodeFile()
        if not node_file.has_local_node():
            return -1, 'no local node'
        node = node_file['local_node']

        _rsa_pub_key, _rsa_pri_key = get_key_obj_from_str(node.node_rsa_public_key, node.node_rsa_private_key)

        # bc = BlockChain()
        share_tx = self.bc.find_tx_by_id(share_tx_id)
        request_tx = self.bc.find_tx_by_id(share_tx.tx_content.previous_tx_id)
        upload_tx = self.bc.find_tx_by_id(request_tx.tx_content.previous_tx_id)

        if not share_tx:
            # print('No found: No block was found with this tx id.')
            return -2, 'not found block'
        elif share_tx.tx_type != 'share':
            # print('Wrong request type: The block you ask does not have share.')
            return -3, 'wrong request type'
        elif node.node_public_key != request_tx.tx_content.source_node_address:
            return -4, 'no authority'
        else:
            data_bundle = DataBundle.deserialize(upload_tx.tx_content.data)
            _data_key_cipher = share_tx.tx_content.key_ciphertext
            data_key = decrypt_rsa(_data_key_cipher, _rsa_pri_key)
            data_bundle.decrypt(data_key)
            return 0, data_bundle

    @staticmethod
    def create_local_node():
        node = Node.generate_node()
        node_file = NodeFile()
        node_file['local_node'] = node
        node_file.save()
        # print('Local node address is: %s' % node.address)
        return 0, node.address

    @staticmethod
    def delete_local_node():
        node_file = NodeFile()
        node_file.delete_local_node()
        local_cnrp = LocalCNRP()
        local_cnrp.delete_local_cnrp()
        if not node_file.has_local_node():
            return 0, 'delete success'
        else:
            return -1, 'delete error'

    # @staticmethod
    def create_genesis_block(self):

        node_file = NodeFile()
        if not node_file.has_local_node():
            return -1, 'no local node'
        node = node_file['local_node']

        # bc = BlockChain()
        tx = self.bc.coin_base_tx(node.address)
        # 如果没有创世块则创建，否则do nothing
        self.bc.new_genesis_block(tx)
        return 0, 'create success'

    @staticmethod
    def receive_data():
        # 创建socket对象
        server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 获取本地主机名
        host = socket.gethostname()
        # 设置端口号
        port = 9000
        # 绑定端口
        server.bind((host, port))
        # 设置最大连接数
        server.listen(5)
        # 建立连接
        while True:
            print('开始监听')
            # 建立客户端连接
            clint, addr = server.accept()
            print("连接地址: %s" % str(addr))

            # # 接收文件属性数据,创建文件
            # jsonObj = json.loads(clint.recv(1024).decode('utf-8'))
            # print('接收到数据', jsonObj)
            # # if not jsonObj:
            # #     os.mknod(jsonObj['name'])
            # msg = json.dumps({'name': '消息', '信号': '创建成功'})
            # # 回数据确认已经建立新文件
            # clint.send(msg.encode('utf-8'))
            # # 接收数据
            # size = 0
            # sizeValue = int(jsonObj['size'])
            # print('开始接收数据')
            # with open(jsonObj['name'], 'wb') as file:
            #     while size < sizeValue:
            #         value = sizeValue - size
            #         if value > 1024:
            #             getdate = clint.recv(1024)
            #         else:
            #             getdate = clint.recv(value)
            #         file.write(getdate)
            #         print('data:', getdate)
            #         size += 1024

            total_data = []
            while True:
                data = clint.recv(20480)
                if not data: break
                total_data.append(data)

            print('结束')

            # 关闭连接
            clint.close()

            res = ''
            for _ in total_data:
                res += str(_, encoding="utf-8")

            print('接受到数据：', res)

            return res

    @staticmethod
    def send_data(address, data_dict):
        # 建立json数据，包含文件名以及大小
        # {name:name,size:99999}
        # date = {}
        # if len(sys.argv) == 2:
        #     print(type(sys.argv[1]))
        #     name = sys.argv[1].split('\\')[-1]
        #     date['name'] = name
        # else:
        #     date['name'] = sys.argv[2]
        # date['size'] = os.path.getsize(sys.argv[1])

        jsonString = json.dumps(data_dict).encode('utf-8')

        # create connection
        clint = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 获取本地主机名
        # host = socket.gethostname()

        # 设置端口
        port = 9000
        # 连接服务，指定主机和端口
        clint.connect((address, port))
        # 发送文件属性信息
        # clint.send(jsonString)
        # 接收服务端发过来的确认信息
        # clint.recv(1024)
        # 发送文件数据
        # size = 0
        # with open(sys.argv[1], 'rb') as file:
        #     while size < date['size']:
        #         fileDate = file.read(1024)
        #         clint.send(fileDate)
        #         size += 1024

        clint.send(jsonString)
        # 关闭连接
        clint.close()

        return

    # @staticmethod
    def get_xtype_num(self, xtype):
        # bc = BlockChain()
        return 0, len(self.bc.find_block_by_type(xtype))

    # @staticmethod
    def get_cur_height(self):
        # bc = BlockChain()
        return 0, self.bc.get_last_block().block_header.height

    # @staticmethod
    def get_global_cnrp_by_height(self, height):
        # bc = BlockChain()
        block = self.bc.get_block_by_height(height)
        return 0, block.block_header.cnrp.get('cnrp_value', {})

    # 返回一个 block object
    def get_last_block_of_chain(self):
        block = self.bc.get_last_block()
        return 0, block

    def get_block_by_height(self, height):
        return 0, self.bc.get_block_by_height(height)

    def get_local_cnrp_dict(self, sort_switch=False):
        local_cnrp = LocalCNRP()
        if not local_cnrp.has_local_cnrp():
            return -1, 'no local node'
        return 0, local_cnrp.get_cnrp_dict(sort_switch)

    # 查看本地数据库
    def db_show(self):
        return 0, self.bc.db.show()

    # 清空（主表）
    def db_clean_up(self):
        self.bc.db.clean_up()
        return 0


# Server starts from here.
if __name__ == "__main__":
    action = Action()

    if bootstrap_host == '192.168.0.0':
        action.create_genesis_block()

    # 启动同步进程
    action.server_start()

